From 5321f8f195c4d88de5638be624988ae95e7399d5 Mon Sep 17 00:00:00 2001 From: Alexander Mikhaylenko Date: Fri, 24 Apr 2020 20:55:43 +0500 Subject: [PATCH] headerbar: Use GtkWindowControls Now that the title buttons are encapsulated in a separate widget, use it in the header bar. Hide them when empty, so that they don't add extra spacing. --- gtk/gtkheaderbar.c | 358 ++++++--------------------------------------- 1 file changed, 48 insertions(+), 310 deletions(-) diff --git a/gtk/gtkheaderbar.c b/gtk/gtkheaderbar.c index 4afaa7dc19..64118ad52e 100644 --- a/gtk/gtkheaderbar.c +++ b/gtk/gtkheaderbar.c @@ -21,21 +21,18 @@ #include "gtkheaderbarprivate.h" -#include "gtkactionable.h" #include "gtkbox.h" -#include "gtkbutton.h" #include "gtkbuildable.h" #include "gtkcenterlayout.h" #include "gtkcssnodeprivate.h" -#include "gtkimage.h" #include "gtkintl.h" #include "gtklabel.h" +#include "gtknative.h" #include "gtkprivate.h" #include "gtksizerequest.h" #include "gtktypebuiltins.h" #include "gtkwidgetprivate.h" -#include "gtkwindowprivate.h" -#include "gtknative.h" +#include "gtkwindowcontrols.h" #include "a11y/gtkcontaineraccessible.h" @@ -68,21 +65,20 @@ * |[ * headerbar * ├── box.start - * │ ╰── box - * │ ├── [image.titlebutton.icon] - * │ ├── [button.titlebutton.minimize] - * │ ├── [button.titlebutton.maximize] - * │ ╰── [button.titlebutton.close] + * │ ├── windowcontrols.start + * │ ╰── [other children] * ├── [Custom Title] * ╰── box.end + * ├── [other children] + * ╰── windowcontrols.end * ]| * - * A #GtkHeaderBar's CSS node is called headerbar. It contains two box subnodes at the start - * and end of the headerbar, as well as a center node that represents the title. + * A #GtkHeaderBar's CSS node is called headerbar. It contains two box subnodes + * at the start and end of the headerbar, as well as a center node that + * represents the title. * - * The titlebuttons get their own box subnode, either in the start box or in the end box. - * Which of the title buttons exist and where they are placed exactly depends on the - * desktop environment. + * Each of the boxes contains a windowcontrols subnode, see #GtkWindowControls + * for details, as well as other children. */ #define MIN_TITLE_CHARS 5 @@ -119,8 +115,8 @@ struct _GtkHeaderBarPrivate gchar *decoration_layout; gboolean track_default_decoration; - GtkWidget *titlebar_start_box; - GtkWidget *titlebar_end_box; + GtkWidget *start_window_controls; + GtkWidget *end_window_controls; GdkSurfaceState state; }; @@ -214,204 +210,31 @@ create_title_box (const char *title, return label_box; } -static gboolean -update_window_icon (GtkHeaderBar *bar, - GtkWindow *window, - GtkWidget *icon) -{ - GdkPaintable *paintable; - gint scale; - - scale = gtk_widget_get_scale_factor (icon); - paintable = gtk_window_get_icon_for_size (window, 20 * scale); - - if (paintable) - { - gtk_image_set_from_paintable (GTK_IMAGE (icon), paintable); - g_object_unref (paintable); - gtk_widget_show (icon); - - return TRUE; - } - - return FALSE; -} - static void -update_window_buttons (GtkHeaderBar *bar) +create_window_controls (GtkHeaderBar *bar) { GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar); - GtkWidget *widget = GTK_WIDGET (bar); - GtkWidget *toplevel; - GtkWindow *window; - gchar *layout_desc; - gchar **tokens, **t; - gint i, j; - gboolean is_sovereign_window; - - if (!gtk_widget_get_realized (widget)) - return; - - toplevel = GTK_WIDGET (gtk_widget_get_root (widget)); - if (!GTK_IS_WINDOW (toplevel)) - return; - - if (priv->titlebar_start_box) - { - gtk_widget_unparent (priv->titlebar_start_box); - priv->titlebar_start_box = NULL; - } - if (priv->titlebar_end_box) - { - gtk_widget_unparent (priv->titlebar_end_box); - priv->titlebar_end_box = NULL; - } - - if (!priv->show_title_buttons) - return; - - if (priv->decoration_layout) - layout_desc = g_strdup (priv->decoration_layout); - else - g_object_get (gtk_widget_get_settings (widget), - "gtk-decoration-layout", &layout_desc, - NULL); - - window = GTK_WINDOW (toplevel); - - is_sovereign_window = !gtk_window_get_modal (window) && - gtk_window_get_transient_for (window) == NULL; - - tokens = g_strsplit (layout_desc, ":", 2); - if (tokens) - { - for (i = 0; i < 2; i++) - { - GtkWidget *box; - int n_children = 0; - - if (tokens[i] == NULL) - break; - - t = g_strsplit (tokens[i], ",", -1); - - box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); - - for (j = 0; t[j]; j++) - { - GtkWidget *button = NULL; - GtkWidget *image = NULL; - AtkObject *accessible; - - if (strcmp (t[j], "icon") == 0 && - is_sovereign_window) - { - button = gtk_image_new (); - gtk_widget_set_valign (button, GTK_ALIGN_CENTER); - gtk_widget_add_css_class (button, "titlebutton"); - gtk_widget_add_css_class (button, "icon"); - - if (!update_window_icon (bar, window, button)) - { - g_object_ref_sink (button); - g_object_unref (button); - button = NULL; - } - } - else if (strcmp (t[j], "minimize") == 0 && - is_sovereign_window) - { - button = gtk_button_new (); - gtk_widget_set_valign (button, GTK_ALIGN_CENTER); - gtk_widget_add_css_class (button, "titlebutton"); - gtk_widget_add_css_class (button, "minimize"); - image = gtk_image_new_from_icon_name ("window-minimize-symbolic"); - g_object_set (image, "use-fallback", TRUE, NULL); - gtk_container_add (GTK_CONTAINER (button), image); - gtk_widget_set_can_focus (button, FALSE); - gtk_actionable_set_action_name (GTK_ACTIONABLE (button), - "window.minimize"); - - accessible = gtk_widget_get_accessible (button); - if (GTK_IS_ACCESSIBLE (accessible)) - atk_object_set_name (accessible, _("Minimize")); - } - else if (strcmp (t[j], "maximize") == 0 && - gtk_window_get_resizable (window) && - is_sovereign_window) - { - const gchar *icon_name; - gboolean maximized = gtk_window_is_maximized (window); - - icon_name = maximized ? "window-restore-symbolic" : "window-maximize-symbolic"; - button = gtk_button_new (); - gtk_widget_set_valign (button, GTK_ALIGN_CENTER); - gtk_widget_add_css_class (button, "titlebutton"); - gtk_widget_add_css_class (button, "maximize"); - image = gtk_image_new_from_icon_name (icon_name); - g_object_set (image, "use-fallback", TRUE, NULL); - gtk_container_add (GTK_CONTAINER (button), image); - gtk_widget_set_can_focus (button, FALSE); - gtk_actionable_set_action_name (GTK_ACTIONABLE (button), - "window.toggle-maximized"); - - accessible = gtk_widget_get_accessible (button); - if (GTK_IS_ACCESSIBLE (accessible)) - atk_object_set_name (accessible, maximized ? _("Restore") : _("Maximize")); - } - else if (strcmp (t[j], "close") == 0 && - gtk_window_get_deletable (window)) - { - button = gtk_button_new (); - gtk_widget_set_valign (button, GTK_ALIGN_CENTER); - image = gtk_image_new_from_icon_name ("window-close-symbolic"); - gtk_widget_add_css_class (button, "titlebutton"); - gtk_widget_add_css_class (button, "close"); - g_object_set (image, "use-fallback", TRUE, NULL); - gtk_container_add (GTK_CONTAINER (button), image); - gtk_widget_set_can_focus (button, FALSE); - gtk_actionable_set_action_name (GTK_ACTIONABLE (button), - "window.close"); - - accessible = gtk_widget_get_accessible (button); - if (GTK_IS_ACCESSIBLE (accessible)) - atk_object_set_name (accessible, _("Close")); - } - - if (button) - { - gtk_container_add (GTK_CONTAINER (box), button); - n_children ++; - } - } - g_strfreev (t); - - if (n_children == 0) - { - g_object_ref_sink (box); - g_object_unref (box); - continue; - } - - if (i == 0) - gtk_widget_add_css_class (box, GTK_STYLE_CLASS_LEFT); - else - gtk_widget_add_css_class (box, GTK_STYLE_CLASS_RIGHT); - - if (i == 0) - { - priv->titlebar_start_box = box; - gtk_container_add (GTK_CONTAINER (priv->start_box), box); - } - else - { - priv->titlebar_end_box = box; - gtk_container_add (GTK_CONTAINER (priv->end_box), box); - } - } - g_strfreev (tokens); - } - g_free (layout_desc); + GtkWidget *controls; + + controls = gtk_window_controls_new (GTK_PACK_START); + g_object_bind_property (bar, "decoration-layout", + controls, "decoration-layout", + G_BINDING_SYNC_CREATE); + g_object_bind_property (controls, "empty", + controls, "visible", + G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); + gtk_container_add (GTK_CONTAINER (priv->start_box), controls); + priv->start_window_controls = controls; + + controls = gtk_window_controls_new (GTK_PACK_END); + g_object_bind_property (bar, "decoration-layout", + controls, "decoration-layout", + G_BINDING_SYNC_CREATE); + g_object_bind_property (controls, "empty", + controls, "visible", + G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); + gtk_container_add (GTK_CONTAINER (priv->end_box), controls); + priv->end_window_controls = controls; } static void @@ -434,7 +257,7 @@ update_default_decoration (GtkHeaderBar *bar) w != NULL; w = _gtk_widget_get_next_sibling (w)) { - if (w != priv->titlebar_start_box) + if (w != priv->start_window_controls) { have_children = TRUE; break; @@ -446,7 +269,7 @@ update_default_decoration (GtkHeaderBar *bar) w != NULL; w = _gtk_widget_get_next_sibling (w)) { - if (w != priv->titlebar_end_box) + if (w != priv->end_window_controls) { have_children = TRUE; break; @@ -872,7 +695,7 @@ gtk_header_bar_forall (GtkContainer *container, { GtkWidget *next = _gtk_widget_get_next_sibling (w); - if (w != priv->titlebar_start_box) + if (w != priv->start_window_controls) (* callback) (w, callback_data); w = next; @@ -889,7 +712,7 @@ gtk_header_bar_forall (GtkContainer *container, { GtkWidget *next = _gtk_widget_get_next_sibling (w); - if (w != priv->titlebar_end_box) + if (w != priv->end_window_controls) (* callback) (w, callback_data); w = next; @@ -903,93 +726,6 @@ gtk_header_bar_child_type (GtkContainer *container) return GTK_TYPE_WIDGET; } -static void surface_state_changed (GtkWidget *widget); - -static void window_notify_cb (GtkHeaderBar *header_bar, - GParamSpec *pspec, - GtkWindow *window); - -static void -gtk_header_bar_realize (GtkWidget *widget) -{ - GtkSettings *settings; - GtkWidget *root; - - GTK_WIDGET_CLASS (gtk_header_bar_parent_class)->realize (widget); - - settings = gtk_widget_get_settings (widget); - g_signal_connect_swapped (settings, "notify::gtk-decoration-layout", - G_CALLBACK (update_window_buttons), widget); - g_signal_connect_swapped (gtk_native_get_surface (gtk_widget_get_native (widget)), "notify::state", - G_CALLBACK (surface_state_changed), widget); - - root = GTK_WIDGET (gtk_widget_get_root (widget)); - - if (GTK_IS_WINDOW (root)) - g_signal_connect_swapped (root, "notify", - G_CALLBACK (window_notify_cb), widget); - - update_window_buttons (GTK_HEADER_BAR (widget)); -} - -static void -gtk_header_bar_unrealize (GtkWidget *widget) -{ - GtkSettings *settings; - - settings = gtk_widget_get_settings (widget); - - g_signal_handlers_disconnect_by_func (settings, update_window_buttons, widget); - g_signal_handlers_disconnect_by_func (gtk_native_get_surface (gtk_widget_get_native (widget)), surface_state_changed, widget); - g_signal_handlers_disconnect_by_func (gtk_widget_get_root (widget), window_notify_cb, widget); - - GTK_WIDGET_CLASS (gtk_header_bar_parent_class)->unrealize (widget); -} - -static void -surface_state_changed (GtkWidget *widget) -{ - GtkHeaderBar *bar = GTK_HEADER_BAR (widget); - GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar); - GdkSurfaceState changed, new_state; - - new_state = gdk_toplevel_get_state (GDK_TOPLEVEL (gtk_native_get_surface (gtk_widget_get_native (widget)))); - changed = new_state ^ priv->state; - priv->state = new_state; - - if (changed & (GDK_SURFACE_STATE_FULLSCREEN | - GDK_SURFACE_STATE_MAXIMIZED | - GDK_SURFACE_STATE_TILED | - GDK_SURFACE_STATE_TOP_TILED | - GDK_SURFACE_STATE_RIGHT_TILED | - GDK_SURFACE_STATE_BOTTOM_TILED | - GDK_SURFACE_STATE_LEFT_TILED)) - update_window_buttons (bar); -} - -static void -window_notify_cb (GtkHeaderBar *header_bar, - GParamSpec *pspec, - GtkWindow *window) -{ - if (pspec->name == I_("deletable") || - pspec->name == I_("icon-name") || - pspec->name == I_("modal") || - pspec->name == I_("resizable") || - pspec->name == I_("transient-for")) - update_window_buttons (header_bar); -} - -static void -gtk_header_bar_root (GtkWidget *widget) -{ - GtkHeaderBar *bar = GTK_HEADER_BAR (widget); - - GTK_WIDGET_CLASS (gtk_header_bar_parent_class)->root (widget); - - update_window_buttons (bar); -} - static void gtk_header_bar_class_init (GtkHeaderBarClass *class) { @@ -1002,10 +738,6 @@ gtk_header_bar_class_init (GtkHeaderBarClass *class) object_class->get_property = gtk_header_bar_get_property; object_class->set_property = gtk_header_bar_set_property; - widget_class->realize = gtk_header_bar_realize; - widget_class->unrealize = gtk_header_bar_unrealize; - widget_class->root = gtk_header_bar_root; - container_class->add = gtk_header_bar_add; container_class->remove = gtk_header_bar_remove; container_class->forall = gtk_header_bar_forall; @@ -1227,7 +959,15 @@ gtk_header_bar_set_show_title_buttons (GtkHeaderBar *bar, return; priv->show_title_buttons = setting; - update_window_buttons (bar); + + if (setting) + create_window_controls (bar); + else + { + g_clear_pointer (&priv->start_window_controls, gtk_widget_unparent); + g_clear_pointer (&priv->end_window_controls, gtk_widget_unparent); + } + g_object_notify_by_pspec (G_OBJECT (bar), header_bar_props[PROP_SHOW_TITLE_BUTTONS]); } @@ -1320,8 +1060,6 @@ gtk_header_bar_set_decoration_layout (GtkHeaderBar *bar, g_free (priv->decoration_layout); priv->decoration_layout = g_strdup (layout); - update_window_buttons (bar); - g_object_notify_by_pspec (G_OBJECT (bar), header_bar_props[PROP_DECORATION_LAYOUT]); } -- 2.30.2